package com.zjlab.sandtable;

import cn.hutool.core.util.ObjectUtil;
import com.zjlab.SmConfig;
import com.zjlab.sandtable.bean.Info;
import com.zjlab.sandtable.handler.CarSpeedInfoHandler;
import com.zjlab.sandtable.handler.CoinsMakeStatusInfoHandler;
import com.zjlab.sandtable.handler.MfLogsInfoHandler;
import com.zjlab.sandtable.handler.SandTableUdpHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xue
 * @create 2022-11-07 13:51
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SandTableUdp {

    private EventLoopGroup group;
    private Channel channel;

    private final CarSpeedInfoHandler carSpeedInfoHandler;
    private final CoinsMakeStatusInfoHandler coinsMakeStatusInfoHandler;
    private final MfLogsInfoHandler mfLogsInfoHandler;
    private final SandTableUdpHandler sandTableUdpHandler;
    private final SmConfig smConfig;

    @PostConstruct
    public void init() throws InterruptedException {
        group = new NioEventLoopGroup(2, new ThreadFactory() {
            private final AtomicInteger count = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "沙盘接收" + count.getAndIncrement());
            }
        });
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                // 主线程处理
                .channel(NioDatagramChannel.class)
                // 广播
                .option(ChannelOption.SO_BROADCAST, true)
                // 设置udp单帧超过2M的办法
                //.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535))
                // 设置读缓冲区为2M
                .option(ChannelOption.SO_RCVBUF, 2048 * 1024)
                // 设置写缓冲区为1M
                .option(ChannelOption.SO_SNDBUF, 1024 * 1024)
                .handler(new ChannelInitializer<NioDatagramChannel>() {

                    @Override
                    protected void initChannel(NioDatagramChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(sandTableUdpHandler);
                        pipeline.addLast(mfLogsInfoHandler);
                        pipeline.addLast(coinsMakeStatusInfoHandler);
                        pipeline.addLast(carSpeedInfoHandler);
                    }
                });

        ChannelFuture f = bootstrap.bind(smConfig.getMiddlewareSandTablePort()).sync();
        channel = f.channel();
        log.info("沙盘控制端口{}正在监听中……", smConfig.getMiddlewareSandTablePort());
        f.channel().closeFuture().addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                log.info("沙盘控制端口{}已关闭", smConfig.getMiddlewareSandTablePort());
            }
        });
    }


    @PreDestroy
    public void stop() {
        log.info("销毁SandTableUdp");
        if (ObjectUtils.isEmpty(group)) {
            group.shutdownGracefully();
        }
    }

    public void sendInfo(Info info) {
        log.info("发送沙盘:{}", info);
        if (ObjectUtil.isNotEmpty(channel) && channel.isActive()) {
            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(
                    smConfig.getSandTableAddress().getIp(),
                    smConfig.getSandTableAddress().getPort()
            );
            channel.writeAndFlush(new DatagramPacket(info.encoding(byteBuf), inetSocketAddress));
        }
    }

    public void sendInfo(Info info, Address address) {
        if (ObjectUtil.isNotEmpty(channel) && channel.isActive()) {
            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(address.getIp(), address.getPort());
            channel.writeAndFlush(new DatagramPacket(info.encoding(byteBuf), inetSocketAddress));
        }
    }

}
